﻿#include "server.hpp"

#include <iostream>
#include <sstream>
#include "json.hpp"
#include "network_interface.hpp"
#include "system_time_ctl.hpp"
#include "time_util.hpp"
#include "user.hpp"

const std::string PATH_CONFIG = "config.json";

static void LogPrint(const std::string& str, const std::string& filename, int line)
{
    std::stringstream ss;
    ss << "[" << cpp::timestamp2Str() << "] " << str
        << " (" << std::filesystem::path{filename}.filename().string() << ":" << line << ")" << std::endl;
    std::cout << ss.str();
}

#define LOG(message) LogPrint(message, __FILE__, __LINE__)

// constexpr uint16_t ListenPort = 5555;
// const std::string MulticastAddress = "224.1.1.100";

bool Server::onInit()
{
    if (!cpp::loadJsonFileToObject(PATH_CONFIG, params))
    {
        LOG(u8"[onInit] load config error.");
        DeviceInfo default_device_info;
        default_device_info.name = "default_device_info";
        default_device_info.serial = User::generate_serial("");
        default_device_info.remark = "remark";
        default_device_info.ntp_url = "cn.ntp.org.cn";
        default_device_info.ip = "0.0.0.0";
        default_device_info.subnet = "255.255.255.0";
        params.device_info = default_device_info;
        if (!cpp::saveObjectToJsonFile(PATH_CONFIG, params))
        {
            LOG(u8"[onInit] save default config error.");
        }
        return false;
    }
    auto interfaces = NetworkInterface::getActiveInterfaces();
    for (const auto& item : interfaces)
    {
        if (item.name == params.network_interface_name)
        {
            params.device_info.ip = item.Addresses[0].first;
            params.device_info.subnet = item.Addresses[0].second;
        }
    }

    LOG("[onInit] Load config success.");
    LOG(std::string{"[onInit] Config: "}.append(cpp::object2Str(params)));

    if (params.device_info.ntp_url.empty() == false)
    {
        if (SystemTimeCtl::syncTime(params.device_info.ntp_url) == false)
        {
            LOG("[onInit] Sync system time fail. ");
        }
        else
        {
            LOG("[onInit] Sync system time success. ");
        }
    }

    sp_broadcast_acceptor = std::make_shared<BroadcastAcceptor>(
        [this](const std::vector<uint8_t>& data, const std::string& ip, uint16_t port)
        {
            onDataReceived(data, ip, port);
        });
    sp_broadcast_sender = std::make_shared<BroadcastSender>();
    auto ret = sp_broadcast_acceptor->init(params.port_broadcast_receiver, "0.0.0.0");
    if (ret.isOK() == false)
    {
        LOG(std::string{"[onInit] broadcast acceptor init error: "} + ret.unwrap_err());
        sp_broadcast_acceptor->release();
        sp_broadcast_acceptor.reset();
        return false;
    }
    ret = sp_broadcast_sender->init(params.port_broadcast_receiver, params.device_info.ip);
    if (ret.isOK() == false)
    {
        LOG(std::string{"[onInit] broadcast sender init error: "} + ret.unwrap_err());
        sp_broadcast_sender->release();
        sp_broadcast_sender.reset();
        return false;
    }

    sp_multicast_acceptor = std::make_shared<MulticastAcceptor>(
        [this](const std::vector<uint8_t>& data, const std::string& ip, uint16_t port)
        {
            onDataReceived(data, ip, port);
        });
    sp_multicast_sender = std::make_shared<MulticastSender>();
    ret = sp_multicast_acceptor->init(params.port_multicast_receiver, params.address_multicast, params.device_info.ip);
    if (ret.isOK() == false)
    {
        LOG(std::string{"[onInit] multicast acceptor init error: "} + ret.unwrap_err());
        sp_multicast_acceptor->release();
        sp_multicast_acceptor.reset();
        return false;
    }
    ret = sp_multicast_sender->init(params.address_multicast, params.port_multicast_receiver, params.device_info.ip);
    if (ret.isOK() == false)
    {
        LOG(std::string{"[onInit] multicast sender init error: "} + ret.unwrap_err());
        sp_broadcast_sender->release();
        sp_broadcast_sender.reset();
        return false;
    }

    LOG("[onInit] Device Find Tool Server launch.");
    return Application<Server>::onInit();
}

int Server::onRun()
{
    sp_broadcast_acceptor->run();
    return Application<Server>::onRun();
}

void Server::onCleanup()
{
    sp_broadcast_acceptor->release();
    sp_broadcast_acceptor.reset();
    sp_broadcast_sender->release();
    sp_broadcast_sender.reset();
    Application<Server>::onCleanup();
}

void Server::onDataReceived(const std::vector<uint8_t>& data, const std::string& ip, uint16_t port)
{
    std::string str;
    str.assign(data.begin(), data.end());
    nlohmann::json req_json;
    try
    {
        req_json = nlohmann::json::parse(str);
        std::vector<std::string> required_fields = {"type", "url", "data"};
        for (auto& item : required_fields)
        {
            if (req_json.contains(item) == false)
            {
                LOG(u8"[onDataReceived] no filed： " + item);
            }
            else
            {
                if (req_json[item].is_null())
                {
                    LOG(u8"[onDataReceived] no filed： " + item);
                }
            }
        }
    }
    catch (const std::exception& e)
    {
        LOG(std::string{"[onDataReceived] find exception"}.append(e.what()));
        return;
    }

    if (req_json["type"] != "request")
    {
        // LOG(u8"[onDataReceived] type is not request.");
        return;
    }

    if (req_json["url"] == "/device/search")
    {
        onHandleDeviceSearch(req_json["data"], ip, port);
    }
    else if (req_json["url"] == "/device/modify")
    {
        onHandleDeviceModify(req_json["data"], ip, port);
    }
    else if (req_json["url"] == "/device/time/set")
    {
        onHandleDeviceTimeSet(req_json["data"], ip, port);
    }
    else if (req_json["url"] == "/device/time/get")
    {
        onHandleDeviceTimeGet(req_json["data"], ip, port);
    }
    else
    {
        std::cout << "url is unknown." << std::endl;
    }
}

void Server::onHandleDeviceSearch(const nlohmann::json& body, const std::string& ip, uint16_t port)
{
    try
    {
        std::vector<std::string> required_fields = {"client_id"};
        for (auto& item : required_fields)
        {
            if (body.contains(item) == false)
            {
                LOG(u8"[onHandleDeviceSearch] no filed： " + item);
            }
            else
            {
                if (body[item].is_null())
                {
                    LOG(u8"[onHandleDeviceSearch] no filed： " + item);
                }
            }
        }


        if (cpp::loadJsonFileToObject(PATH_CONFIG, params) == false)
        {
            LOG(u8"[onHandleDeviceSearch] load config error.");
            return;
        }

        auto interfaces = NetworkInterface::getActiveInterfaces();
        std::string new_ip = "0.0.0.0";
        std::string new_subnet = "255.255.255.0";
        for (const auto& item : interfaces)
        {
            if (item.name == params.network_interface_name)
            {
                new_ip = item.Addresses[0].first;
                new_subnet = item.Addresses[0].second;
                break;
            }
        }

        nlohmann::json rsp_json;
        rsp_json["type"] = "response";
        rsp_json["url"] = "/device/search";
        rsp_json["client_id"] = body["client_id"];
        rsp_json["data"] = std::map<std::string, std::string>{
            {"name", params.device_info.name},
            {"serial", params.device_info.serial},
            {"remark", params.device_info.remark},
            {"ip", new_ip},
            {"subnet", new_subnet},
            {"ntp_url", params.device_info.ntp_url},
        };
        std::string rsp_str = rsp_json.dump();
        std::vector<uint8_t> rsp_data(rsp_str.begin(), rsp_str.end());
        if (sp_broadcast_sender && sp_broadcast_sender->send(rsp_data))
        {
            LOG(u8"[onHandleDeviceSearch] sp_broadcast_sender send device search success.");
        }
        else
        {
            LOG(u8"[onHandleDeviceSearch] sp_broadcast_sender send device search fail.");
        }
        if (sp_multicast_sender && sp_multicast_sender->send(rsp_data))
        {
            LOG(u8"[onHandleDeviceSearch] sp_multicast_sender send device search success.");
        }
        else
        {
            LOG(u8"[onHandleDeviceSearch] sp_multicast_sender send device search fail.");
        }
    }
    catch (const std::exception& e)
    {
        LOG(std::string{"[onHandleDeviceSearch] find exception"}.append(e.what()));
    }
}

void Server::onHandleDeviceModify(const nlohmann::json& body, const std::string& ip, uint16_t port)
{
    std::vector<std::string> required_fields = {"serial", "name", "ip", "subnet", "ntp_url", "client_id"};
    for (auto& item : required_fields)
    {
        if (body.contains(item) == false)
        {
            LOG(u8"[onHandleDeviceModify] no filed： " + item);
        }
        else
        {
            if (body[item].is_null())
            {
                LOG(u8"[onHandleDeviceModify] no filed： " + item);
            }
        }
    }
    if (cpp::loadJsonFileToObject(PATH_CONFIG, params) == false)
    {
        LOG(u8"[onHandleDeviceModify] load config error.");
        return;
    }

    if (body["serial"] != params.device_info.serial)
    {
        // LOG("[onHandleDeviceModify] serial is error.");
        return;
    }
    DeviceInfo new_device_info;
    try
    {
        nlohmann::from_json(body, new_device_info);
        new_device_info.ip = body["ip"];
        new_device_info.subnet = body["subnet"];
    }
    catch (const std::exception& e)
    {
        LOG(std::string{u8"[onHandleDeviceModify] find exception. "}.append(e.what()));
        return;
    }
    bool ret = true;
    if (new_device_info.ip != params.device_info.ip || new_device_info.subnet != params.device_info.subnet)
    {
        ret = NetworkInterface::setInterfaceConfig(params.network_interface_name, new_device_info.ip,
                                                   new_device_info.subnet);
        if (ret == true)
        {
            auto interfaces = NetworkInterface::getActiveInterfaces();
            std::string InterfaceAddress = "0.0.0.0";
            for (const auto& item : interfaces)
            {
                if (item.name == params.network_interface_name)
                {
                    InterfaceAddress = item.Addresses[0].first;
                    break;
                }
            }
            sp_broadcast_acceptor->release();
            auto ret_init = sp_broadcast_acceptor->init(params.port_broadcast_receiver, "0.0.0.0");
            if (ret_init.isOK() == false)
            {
                LOG(std::string{"[onHandleDeviceModify] broadcast acceptor re-init error: "} + ret_init.unwrap_err());
                sp_broadcast_acceptor->release();
                sp_broadcast_acceptor.reset();
                return;
            }
            sp_broadcast_sender->release();
            ret_init = sp_broadcast_sender->init(params.port_broadcast_receiver, InterfaceAddress);
            if (ret_init.isOK() == false)
            {
                LOG(std::string{"[onHandleDeviceModify] broadcast sender re-init error: "} + ret_init.unwrap_err());
                sp_broadcast_sender->release();
                sp_broadcast_sender.reset();
                return;
            }
            ret_init = sp_multicast_sender->init(params.address_multicast, params.port_multicast_receiver,
                                            params.device_info.ip);
            if (ret_init.isOK() == false)
            {
                LOG(std::string{"[onHandleDeviceModify] multicast acceptor init error: "} + ret_init.unwrap_err());
                sp_multicast_sender->release();
                sp_multicast_sender.reset();
                return;
            }
            ret_init = sp_multicast_sender->init(params.address_multicast, params.port_multicast_receiver,
                                            params.device_info.ip);
            if (ret_init.isOK() == false)
            {
                LOG(std::string{"[onHandleDeviceModify] multicast sender init error: "} + ret_init.unwrap_err());
                sp_broadcast_sender->release();
                sp_broadcast_sender.reset();
                return;
            }
        }
    }
    if (ret == true)
    {
        params.device_info = new_device_info;
        ret = cpp::saveObjectToJsonFile(PATH_CONFIG, params);
    }

    nlohmann::json rsp_json;
    rsp_json["type"] = "response";
    rsp_json["url"] = "/device/modify";
    rsp_json["client_id"] = body["client_id"];
    rsp_json["data"]["flag"] = ret;
    std::string rsp_str = rsp_json.dump();
    std::vector<uint8_t> rsp_data(rsp_str.begin(), rsp_str.end());
    if (sp_broadcast_sender && sp_broadcast_sender->send(rsp_data))
    {
        LOG(u8"[onHandleDeviceModify] sp_broadcast_sender send device modify response success.");
    }
    else
    {
        LOG(u8"[onHandleDeviceModify] sp_broadcast_sender send device modify response fail.");
    }
    if (sp_multicast_sender && sp_multicast_sender->send(rsp_data))
    {
        LOG(u8"[onHandleDeviceModify] sp_multicast_sender send device modify response success.");
    }
    else
    {
        LOG(u8"[onHandleDeviceModify] sp_multicast_sender send device modify response fail.");
    }
}

void Server::onHandleDeviceTimeSet(const nlohmann::json& body, const std::string& ip, uint16_t port)
{
    std::vector<std::string> required_fields = {"client_id", "timestamp_ms", "serial"};
    for (auto& item : required_fields)
    {
        if (body.contains(item) == false)
        {
            LOG(u8"[onHandleDeviceTimeSet] no filed： " + item);
        }
        else
        {
            if (body[item].is_null())
            {
                LOG(u8"[onHandleDeviceTimeSet] no filed： " + item);
            }
        }
    }

    if (cpp::loadJsonFileToObject(PATH_CONFIG, params) == false)
    {
        LOG(u8"[onHandleDeviceTimeSet] load config error.");
        return;
    }

    if (body["serial"] != params.device_info.serial)
    {
        LOG("[onHandleDeviceTimeSet] serial is error.");
        return;
    }
    nlohmann::json rsp_json;
    rsp_json["type"] = "response";
    rsp_json["url"] = "/device/time/set";
    rsp_json["client_id"] = body["client_id"];
    auto ret = SystemTimeCtl::setTime(body["timestamp_ms"]);
    rsp_json["data"]["flag"] = ret.isOK();
    std::string rsp_str = rsp_json.dump();
    std::vector<uint8_t> rsp_data(rsp_str.begin(), rsp_str.end());
    if (sp_broadcast_sender && sp_broadcast_sender->send(rsp_data))
    {
        LOG(u8"[onHandleDeviceTimeSet] sp_broadcast_sender send device time set response success.");
    }
    else
    {
        LOG(u8"[onHandleDeviceTimeSet] sp_broadcast_sender send device time set response fail.");
    }
    if (sp_multicast_sender && sp_multicast_sender->send(rsp_data))
    {
        LOG(u8"[onHandleDeviceTimeSet] sp_multicast_sender send device time set response success.");
    }
    else
    {
        LOG(u8"[onHandleDeviceTimeSet] sp_multicast_sender send device time set response fail.");
    }
}

void Server::onHandleDeviceTimeGet(const nlohmann::json& body, const std::string& ip, uint16_t port)
{
    std::vector<std::string> required_fields = {"client_id", "serial"};
    for (auto& item : required_fields)
    {
        if (body.contains(item) == false)
        {
            LOG(u8"[onHandleDeviceTimeGet] no filed： " + item);
        }
        else
        {
            if (body[item].is_null())
            {
                LOG(u8"[onHandleDeviceTimeGet] no filed： " + item);
            }
        }
    }

    if (cpp::loadJsonFileToObject(PATH_CONFIG, params) == false)
    {
        LOG(u8"[onHandleDeviceTimeGet] load config error.");
        return;
    }

    if (body["serial"] != params.device_info.serial)
    {
        LOG("[onHandleDeviceTimeGet] serial is error.");
        return;
    }
    nlohmann::json rsp_json;
    rsp_json["type"] = "response";
    rsp_json["url"] = "/device/time/get";
    rsp_json["client_id"] = body["client_id"];
    rsp_json["data"]["timestamp_ms"] = SystemTimeCtl::getCurrentTime();
    std::string rsp_str = rsp_json.dump();
    std::vector<uint8_t> rsp_data(rsp_str.begin(), rsp_str.end());
    if (sp_broadcast_sender && sp_broadcast_sender->send(rsp_data))
    {
        // Log(u8"send device time get response success.");
    }
    else
    {
        LOG(u8"[onHandleDeviceTimeGet] sp_broadcast_sender send device time get response fail.");
    }
    if (sp_multicast_sender && sp_multicast_sender->send(rsp_data))
    {
        // Log(u8"send device time get response success.");
    }
    else
    {
        LOG(u8"[onHandleDeviceTimeGet] sp_multicast_sender send device time get response fail.");
    }
}
